package com.taobao.tddl.dbsync.binlog.event;

import com.taobao.tddl.dbsync.binlog.LogBuffer;
import com.taobao.tddl.dbsync.binlog.LogEvent;

/**
 * The Common-Header, documented in the table @ref Table_common_header "below",
 * always has the same form and length within one version of MySQL. Each event
 * type specifies a format and length of the Post-Header. The length of the
 * Common-Header is the same for all events of the same type. The Body may be of
 * different format and length even for different events of the same type. The
 * binary formats of Post-Header and Body are documented separately in each
 * subclass. The binary format of Common-Header is as follows.
 * 
 * <table>
 * <caption>Common-Header</caption>
 * 
 * <tr>
 * <th>Name</th>
 * <th>Format</th>
 * <th>Description</th>
 * </tr>
 * 
 * <tr>
 * <td>timestamp</td>
 * <td>4 byte unsigned integer</td>
 * <td>The time when the query started, in seconds since 1970.</td>
 * </tr>
 * 
 * <tr>
 * <td>type</td>
 * <td>1 byte enumeration</td>
 * <td>See enum #Log_event_type.</td>
 * </tr>
 * 
 * <tr>
 * <td>server_id</td>
 * <td>4 byte unsigned integer</td>
 * <td>Server ID of the server that created the event.</td>
 * </tr>
 * 
 * <tr>
 * <td>total_size</td>
 * <td>4 byte unsigned integer</td>
 * <td>The total size of this event, in bytes. In other words, this is the sum
 * of the sizes of Common-Header, Post-Header, and Body.</td>
 * </tr>
 * 
 * <tr>
 * <td>master_position</td>
 * <td>4 byte unsigned integer</td>
 * <td>The position of the next event in the master binary log, in bytes from
 * the beginning of the file. In a binlog that is not a relay log, this is just
 * the position of the next event, in bytes from the beginning of the file. In a
 * relay log, this is the position of the next event in the master's binlog.</td>
 * </tr>
 * 
 * <tr>
 * <td>flags</td>
 * <td>2 byte bitfield</td>
 * <td>See Log_event::flags.</td>
 * </tr>
 * </table>
 * 
 * Summing up the numbers above, we see that the total size of the common header
 * is 19 bytes.
 * 
 * @see mysql-5.1.60/sql/log_event.cc
 * 
 * @author <a href="mailto:changyuan.lh@taobao.com">Changyuan.lh</a>
 * @version 1.0
 */
public final class LogHeader
{
    protected final int type;

    /**
     * The offset in the log where this event originally appeared (it is
     * preserved in relay logs, making SHOW SLAVE STATUS able to print
     * coordinates of the event in the master's binlog). Note: when a
     * transaction is written by the master to its binlog (wrapped in
     * BEGIN/COMMIT) the log_pos of all the queries it contains is the one of
     * the BEGIN (this way, when one does SHOW SLAVE STATUS it sees the offset
     * of the BEGIN, which is logical as rollback may occur), except the COMMIT
     * query which has its real offset.
     */
    protected long      logPos;

    /**
     * Timestamp on the master(for debugging and replication of
     * NOW()/TIMESTAMP). It is important for queries and LOAD DATA INFILE. This
     * is set at the event's creation time, except for Query and Load (et al.)
     * events where this is set at the query's execution time, which guarantees
     * good replication (otherwise, we could have a query and its event with
     * different timestamps).
     */
    protected long      when;

    /** Number of bytes written by write() function */
    protected int       eventLen;

    /**
     * The master's server id (is preserved in the relay log; used to prevent
     * from infinite loops in circular replication).
     */
    protected long      serverId;

    /**
     * Some 16 flags. See the definitions above for LOG_EVENT_TIME_F,
     * LOG_EVENT_FORCED_ROTATE_F, LOG_EVENT_THREAD_SPECIFIC_F, and
     * LOG_EVENT_SUPPRESS_USE_F for notes.
     */
    protected int       flags;
    
    /** 
     * The value is set by caller of FD constructor and
     * Log_event::write_header() for the rest.
     * In the FD case it's propagated into the last byte 
     * of post_header_len[] at FD::write().
     * On the slave side the value is assigned from post_header_len[last] 
     * of the last seen FD event.
     */
    protected int       checksumAlg;
    /**
       Placeholder for event checksum while writing to binlog.
    */
    protected long      crc;      // ha_checksum

    /* for Start_event_v3 */
    public LogHeader(final int type)
    {
        this.type = type;
    }

    public LogHeader(LogBuffer buffer,
            FormatDescriptionLogEvent descriptionEvent)
    {
        when = buffer.getUint32();
        type = buffer.getUint8(); // LogEvent.EVENT_TYPE_OFFSET;
        serverId = buffer.getUint32(); // LogEvent.SERVER_ID_OFFSET;
        eventLen = (int) buffer.getUint32(); // LogEvent.EVENT_LEN_OFFSET;
        
        if (descriptionEvent.binlogVersion == 1)
        {
            logPos = 0;
            flags = 0;
            return;
        }

        /* 4.0 or newer */
        logPos = buffer.getUint32(); // LogEvent.LOG_POS_OFFSET
        /*
          If the log is 4.0 (so here it can only be a 4.0 relay log read by
          the SQL thread or a 4.0 master binlog read by the I/O thread),
          log_pos is the beginning of the event: we transform it into the end
          of the event, which is more useful.
          But how do you know that the log is 4.0: you know it if
          description_event is version 3 *and* you are not reading a
          Format_desc (remember that mysqlbinlog starts by assuming that 5.0
          logs are in 4.0 format, until it finds a Format_desc).
        */
        if (descriptionEvent.binlogVersion == 3
                && type < LogEvent.FORMAT_DESCRIPTION_EVENT && logPos != 0)
        {
            /*
              If log_pos=0, don't change it. log_pos==0 is a marker to mean
              "don't change rli->group_master_log_pos" (see
              inc_group_relay_log_pos()). As it is unreal log_pos, adding the
              event len's is nonsense. For example, a fake Rotate event should
              not have its log_pos (which is 0) changed or it will modify
              Exec_master_log_pos in SHOW SLAVE STATUS, displaying a nonsense
              value of (a non-zero offset which does not exist in the master's
              binlog, so which will cause problems if the user uses this value
              in CHANGE MASTER).
            */
            logPos += eventLen; /* purecov: inspected */
        }

        flags = buffer.getUint16(); // LogEvent.FLAGS_OFFSET
        if ((type == LogEvent.FORMAT_DESCRIPTION_EVENT)
                || (type == LogEvent.ROTATE_EVENT))
        {
            /*
              These events always have a header which stops here (i.e. their
              header is FROZEN).
            */
            /*
              Initialization to zero of all other Log_event members as they're
              not specified. Currently there are no such members; in the future
              there will be an event UID (but Format_description and Rotate
              don't need this UID, as they are not propagated through
              --log-slave-updates (remember the UID is used to not play a query
              twice when you have two masters which are slaves of a 3rd master).
              Then we are done.
            */
            
            if (type == LogEvent.FORMAT_DESCRIPTION_EVENT) {
                int commonHeaderLen = buffer.getUint8(FormatDescriptionLogEvent.LOG_EVENT_MINIMAL_HEADER_LEN
                                                      + FormatDescriptionLogEvent.ST_COMMON_HEADER_LEN_OFFSET);
                buffer.position(commonHeaderLen + FormatDescriptionLogEvent.ST_SERVER_VER_OFFSET);
                String serverVersion = buffer.getFixString(FormatDescriptionLogEvent.ST_SERVER_VER_LEN); // ST_SERVER_VER_OFFSET
                int versionSplit[] = new int[] { 0, 0, 0 };
                FormatDescriptionLogEvent.doServerVersionSplit(serverVersion, versionSplit);
                checksumAlg = LogEvent.BINLOG_CHECKSUM_ALG_UNDEF;
                if (FormatDescriptionLogEvent.versionProduct(versionSplit) >= FormatDescriptionLogEvent.checksumVersionProduct) {
                    buffer.position(eventLen - LogEvent.BINLOG_CHECKSUM_LEN - LogEvent.BINLOG_CHECKSUM_ALG_DESC_LEN);
                    checksumAlg = buffer.getUint8();
                }

                processCheckSum(buffer);
            }
            return;
        }
        
        /*
        CRC verification by SQL and Show-Binlog-Events master side.
        The caller has to provide @description_event->checksum_alg to
        be the last seen FD's (A) descriptor.
        If event is FD the descriptor is in it.
        Notice, FD of the binlog can be only in one instance and therefore
        Show-Binlog-Events executing master side thread needs just to know
        the only FD's (A) value -  whereas RL can contain more.
        In the RL case, the alg is kept in FD_e (@description_event) which is reset 
        to the newer read-out event after its execution with possibly new alg descriptor.
        Therefore in a typical sequence of RL:
        {FD_s^0, FD_m, E_m^1} E_m^1 
        will be verified with (A) of FD_m.

        See legends definition on MYSQL_BIN_LOG::relay_log_checksum_alg docs
        lines (log.h).

        Notice, a pre-checksum FD version forces alg := BINLOG_CHECKSUM_ALG_UNDEF.
        */
        checksumAlg = descriptionEvent.getHeader().checksumAlg; // fetch checksum alg
        processCheckSum(buffer);
        /* otherwise, go on with reading the header from buf (nothing now) */
    }

    /**
     * The different types of log events.
     */
    public final int getType()
    {
        return type;
    }

    /**
     * The position of the next event in the master binary log, in bytes from
     * the beginning of the file. In a binlog that is not a relay log, this is
     * just the position of the next event, in bytes from the beginning of the
     * file. In a relay log, this is the position of the next event in the
     * master's binlog.
     */
    public final long getLogPos()
    {
        return logPos;
    }

    /**
     * The total size of this event, in bytes. In other words, this is the sum
     * of the sizes of Common-Header, Post-Header, and Body.
     */
    public final int getEventLen()
    {
        return eventLen;
    }

    /**
     * The time when the query started, in seconds since 1970.
     */
    public final long getWhen()
    {
        return when;
    }

    /**
     * Server ID of the server that created the event.
     */
    public final long getServerId()
    {
        return serverId;
    }

    /**
     * Some 16 flags. See the definitions above for LOG_EVENT_TIME_F,
     * LOG_EVENT_FORCED_ROTATE_F, LOG_EVENT_THREAD_SPECIFIC_F, and
     * LOG_EVENT_SUPPRESS_USE_F for notes.
     */
    public final int getFlags()
    {
        return flags;
    }

    
    public long getCrc() {
        return crc;
    }
    
    
    public int getChecksumAlg() {
        return checksumAlg;
    }

    private void processCheckSum(LogBuffer buffer) {
        if (checksumAlg != LogEvent.BINLOG_CHECKSUM_ALG_OFF &&
                checksumAlg != LogEvent.BINLOG_CHECKSUM_ALG_UNDEF){
            crc = buffer.getUint32(eventLen -  LogEvent.BINLOG_CHECKSUM_LEN);
        }
    }
}
